/**
    ******************************************************************************
    * @file    main.c 
    * @author  18408107475@163.com
    * @version v1.0
    * @date    2025/03/30
    * @brief   利用系统滴答定时器进行精准延时
    * @details 
    *   - 实现微秒/毫秒/秒级延时功能
    *   - 通过PE4按键触发中断控制LED状态
    *   - 主循环实现LED交替闪烁
    *   - 蜂鸣器控制接口预留
    ******************************************************************************
**/

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f10x.h"  // STM32标准外设库头文件，包含寄存器定义和库函数声明

/* 函数声明 ------------------------------------------------------------------*/
void LED_STD_Config(void);     // LED初始化函数
void KEY_STD_Config(void);     // 按键中断配置函数
void BEEP_STD_Config(void);    // 蜂鸣器初始化函数

/* 宏定义 --------------------------------------------------------------------*/
#define SYSTICK_CLK_HZ 9000000u  // 系统滴答定时器时钟频率(9MHz)

/* 全局变量 ------------------------------------------------------------------*/
// 无全局变量（符合嵌入式开发减少全局变量的最佳实践）

/**
    ******************************************************************************
    * 函数名称：EXTI4_IRQHandler
    * 功能描述：EXTI4中断服务函数，处理PE4引脚的中断事件
    * 输入参数：无
    * 返回值  ：无
    * 注意事项：
    *   - 检测到PE4下降沿时，关闭PB5 LED，点亮PE5 LED
    *   - 必须清除中断挂起位防止重复进入中断
    *   - 中断响应时间需控制在设计允许范围内
    ******************************************************************************
**/
void EXTI4_IRQHandler(void)
{
    /* 检查EXTI4线路是否触发中断 */
    if(EXTI_GetITStatus(EXTI_Line4) != RESET)
    {
        /* 控制LED状态 */
        GPIO_ResetBits(GPIOB, GPIO_Pin_5);  // PB5输出低电平（LED亮）
        GPIO_SetBits(GPIOE, GPIO_Pin_5);    // PE5输出高电平（LED灭）
        
        /* 清除EXTI4中断挂起标志 */
        EXTI_ClearITPendingBit(EXTI_Line4); 
        
        /* 此处可添加消抖处理或状态标志设置 */
    }
}

/**
    ******************************************************************************
    * 函数名称：LED_STD_Config
    * 功能描述：初始化PB5和PE5引脚为推挽输出模式，配置LED初始状态
    * 输入参数：无
    * 返回值  ：无
    * 配置流程：
    *   1. 开启GPIOB/GPIOE时钟
    *   2. 配置PB5和PE5为50MHz推挽输出
    *   3. 设置PB5高电平（LED灭）、PE5低电平（LED亮）
    * 注意事项：
    *   - LED电路应为低电平驱动（常见连接方式）
    *   - 推挽输出适合直接驱动LED
    ******************************************************************************
**/
void LED_STD_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;  // GPIO配置结构体

    /* 开启GPIOB和GPIOE时钟（APB2总线） */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE);

    /* 配置PB5引脚 */
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_5;        // 选择引脚5
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  // 高速模式（LED无需高速，但无不利影响）
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;  // 推挽输出（适合直接驱动LED）
    GPIO_Init(GPIOB, &GPIO_InitStructure);             // 应用配置到GPIOB

    /* 配置PE5引脚 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;         // 选择引脚5
    GPIO_Init(GPIOE, &GPIO_InitStructure);            // 应用配置到GPIOE

    /* 设置初始状态 */
    GPIO_SetBits(GPIOB, GPIO_Pin_5);   // PB5高电平（LED灭）
    GPIO_ResetBits(GPIOE, GPIO_Pin_5); // PE5低电平（LED亮）
}

/**
    ******************************************************************************
    * 函数名称：BEEP_STD_Config
    * 功能描述：初始化PB8引脚为蜂鸣器控制引脚
    * 输入参数：无
    * 返回值  ：无
    * 配置流程：
    *   1. 开启GPIOB时钟
    *   2. 配置PB8为50MHz推挽输出
    *   3. 初始状态低电平（蜂鸣器关闭）
    * 注意事项：
    *   - 蜂鸣器驱动电路需根据实际硬件设计（有源/无源）
    *   - 推挽输出可提供足够驱动电流
    ******************************************************************************
**/
void BEEP_STD_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    /* 开启GPIOB时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    /* 配置PB8引脚 */
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8;          // 蜂鸣器控制引脚
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_Out_PP;    // 推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    // 高速模式
    GPIO_Init(GPIOB, &GPIO_InitStruct);               // 应用配置

    /* 初始状态：蜂鸣器关闭 */
    GPIO_ResetBits(GPIOB, GPIO_Pin_8);  // 低电平不触发蜂鸣器
}

/**
    ******************************************************************************
    * 函数名称：KEY_STD_Config
    * 功能描述：配置PE4引脚为按键输入，设置外部中断和NVIC
    * 输入参数：无
    * 返回值  ：无
    * 配置流程：
    *   1. 开启GPIOE时钟
    *   2. 配置PE4为浮空输入模式
    *   3. 开启AFIO时钟（重映射功能）
    *   4. 将PE4映射到EXTI4中断线
    *   5. 配置EXTI4为下降沿触发
    *   6. 设置NVIC优先级并启用中断
    * 注意事项：
    *   - 浮空输入需外部电路提供上拉/下拉
    *   - 实际应用应添加按键消抖处理
    *   - 中断优先级设置为最低（0x0F）
    ******************************************************************************
**/
void KEY_STD_Config(void)
{
    EXTI_InitTypeDef   EXTI_InitStructure;
    GPIO_InitTypeDef   GPIO_InitStructure;
    NVIC_InitTypeDef   NVIC_InitStructure;

    /* 开启GPIOE时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

    /* 配置PE4为浮空输入（依赖外部电路上拉） */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  // 浮空输入模式
    GPIO_Init(GPIOE, &GPIO_InitStructure);

    /* 开启AFIO时钟（EXTI配置需要） */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    /* 将PE4映射到EXTI4中断线 */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);

    /* 配置EXTI4参数 */
    EXTI_InitStructure.EXTI_Line = EXTI_Line4;           // 中断线4
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;  // 中断模式
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;            // 使能中断线
    EXTI_Init(&EXTI_InitStructure);

    /* 配置NVIC */
    NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;     // 中断通道EXTI4
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F; // 抢占优先级15（最低）
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;        // 子优先级15
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;      // 启用中断通道
    NVIC_Init(&NVIC_InitStructure);
}

/**
	******************************************************************************
	* @brief  微秒级延时
	* @param  xus 延时时长，范围：0 ~ 1864134 us
	* @note   选择外部时钟STCLK —— 9MHz
	* @retval 无
	* @detail 工作原理：
	*   - SysTick为24位递减计数器
	*   - 计算公式：LOAD = (时钟频率/1000000) * xus - 1
	*   - 9MHz时钟时，1us需要9个时钟周期
	* 注意事项：
	*   - 最大延时受24位计数器限制
	*   - 不适用于在中断中调用
	******************************************************************************
*/
void Delay_us(uint32_t xus)
{
    SysTick->CTRL = 0;                      // 关闭SysTick
    SysTick->LOAD = (9 * xus) - 1;          // 设置重装载值(9MHz时钟下)
    SysTick->VAL = 0x00;                    // 清除当前值
    SysTick->CTRL = 0x00000001;             // 启用SysTick（使用STCLK）
    while(!(SysTick->CTRL & 0x00010000));   // 等待COUNTFLAG置位
    SysTick->CTRL = 0;                      // 关闭SysTick
}

/**
	******************************************************************************
	* @brief  毫秒级延时
	* @param  xms 延时时长，范围：0~1864 ms
	* @note   基于Delay_us实现
	* @retval 无
	* 注意事项：
	*   - 实际最大延时受Delay_us限制
	*   - 存在函数调用开销，精度略低于直接使用SysTick
	******************************************************************************
*/
void Delay_ms(uint32_t xms)
{
    while(xms--)
    {
        Delay_us(1000);  // 累计延时
    }
}

/**
	******************************************************************************
	* @brief  秒级延时
	* @param  xs 延时时长，范围：0~1 s
	* @note   基于Delay_ms实现
	* @retval 无
	* 注意事项：
	*   - 适用于非精确长延时
	*   - 长时间阻塞式延时影响系统实时性
	******************************************************************************
*/
void Delay_s(uint32_t xs)
{
    while(xs--)
    {
        Delay_ms(1000);  // 累计延时
    }
} 

/**
    ******************************************************************************
    * 函数名称：main
    * 功能描述：主函数，初始化外设后进入待机循环
    * 输入参数：无
    * 返回值  ：int类型（实际不会返回）
    * 执行流程：
    *   1. 初始化LED、按键中断、蜂鸣器
    *   2. 进入无限循环，实现LED交替闪烁
    * 注意事项：
    *   - 主循环中应避免长时间阻塞
    *   - 实际应用建议使用状态机代替延时
    ******************************************************************************
**/
int main(void)
{
    /* 外设初始化 */
    LED_STD_Config();    // 初始化LED引脚
    KEY_STD_Config();    // 配置按键中断
    BEEP_STD_Config();   // 初始化蜂鸣器

    /* 主循环 */
    while(1)
    {
        // 控制PB5和PE5 LED交替闪烁
        GPIO_ResetBits(GPIOB, GPIO_Pin_5);  // PB5 LED亮
        GPIO_SetBits(GPIOE, GPIO_Pin_5);    // PE5 LED灭
        Delay_ms(500);                      // 延时500ms
        
        GPIO_SetBits(GPIOB, GPIO_Pin_5);    // PB5 LED灭
        GPIO_ResetBits(GPIOE, GPIO_Pin_5);  // PE5 LED亮
        Delay_ms(500);                      // 延时500ms
        
        /* 蜂鸣器控制示例（需根据硬件设计启用） */
        // GPIO_ToggleBits(GPIOB, GPIO_Pin_8); // 蜂鸣器状态切换
    }
}

/*************************************END OF FILE**************************************/